package com.example.test.dialogfactory;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelKt;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.ExperimentalPagingApi;
import androidx.paging.LoadType;
import androidx.paging.Pager;
import androidx.paging.PagingConfig;
import androidx.paging.PagingData;
import androidx.paging.PagingDataAdapter;
import androidx.paging.PagingSource;
import androidx.paging.PagingSource.LoadResult.Page;
import androidx.paging.PagingState;
import androidx.paging.RemoteMediator.MediatorResult.Error;
import androidx.paging.RemoteMediator.MediatorResult.Success;
import androidx.paging.rxjava3.PagingRx;
import androidx.paging.rxjava3.RxRemoteMediator;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.room.Dao;
import androidx.room.Database;
import androidx.room.Delete;
import androidx.room.Entity;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.PrimaryKey;
import androidx.room.Query;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.OkHttpsException;
import com.google.gson.Gson;
import easier.safe.gson.GsonFactory;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.SingleSource;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import kotlin.jvm.functions.Function0;
import kotlinx.coroutines.CoroutineScope;

public class MainActivity54 extends AppCompatActivity {

    private static final String url = "https://api.github.com/search/repositories?sort=stars&q=Android";
    private RecyclerView mRecycler;

    public static void start( Context context ) {

        Intent starter = new Intent( context, MainActivity54.class );
        context.startActivity( starter );
    }

    @Override
    protected void onCreate( Bundle savedInstanceState ) {

        super.onCreate( savedInstanceState );
        setContentView( R.layout.activity_main54 );

        initView();

        mRecycler.setLayoutManager( new LinearLayoutManager( this ) );
        Adapter adapter = new Adapter();

        GithubDataBase roomDatabase = Room.databaseBuilder( getApplicationContext(), GithubDataBase.class,
            "github_database" ).build();
        GithubDataDao mDao = roomDatabase.githubDataDao();

        @OptIn( markerClass = ExperimentalPagingApi.class )
        Pager< Integer, GithubData > pager = new Pager<>(
            new PagingConfig( 15 ),
            null,
            new GithubRemoteMediator( mDao ),
            new Function0< PagingSource< Integer, GithubData > >() {
                @Override
                public PagingSource< Integer, GithubData > invoke() {

                    return mDao.pagingSource();
                }
            }
        );

        View54Model view54Model = new ViewModelProvider( this ).get( View54Model.class );
        CoroutineScope viewModelScope = ViewModelKt.getViewModelScope( view54Model );

        Flowable< PagingData< GithubData > > flowable = PagingRx.getFlowable( pager );
        Flowable< PagingData< GithubData > > cachedIn = PagingRx.cachedIn( flowable, viewModelScope );

        cachedIn.subscribe(
            new Consumer< PagingData< GithubData > >() {
                @Override
                public void accept( PagingData< GithubData > githubDataPagingData ) throws Throwable {

                    adapter.submitData( getLifecycle(), githubDataPagingData );
                }
            }, new Consumer< Throwable >() {
                @Override
                public void accept( Throwable throwable ) throws Throwable {

                }
            } );

    }

    private void initView() {

        mRecycler = (RecyclerView) findViewById( R.id.recycler );
    }

    public static class View54Model extends ViewModel {

    }

    @Entity( tableName = "GithubData" )
    public static class GithubData {

        @PrimaryKey
        public int id;
        public int stargazers_count;
        public String name;
        public String description;

        @Override
        public String toString() {

            return "Data{" +
                "id=" + id +
                ", stargazers_count=" + stargazers_count +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                '}';
        }
    }

    @Dao
    public interface GithubDataDao {

        @Insert( onConflict = OnConflictStrategy.REPLACE )
        void insertAll( List< GithubData > data );

        @Query( "DELETE FROM GithubData" )
        int clearAll();

        @Delete
        void delete( GithubData data );

        @Query( "SELECT * FROM GithubData" )
        PagingSource< Integer, GithubData > pagingSource();
    }

    @Database( entities = GithubData.class, version = 1, exportSchema = false )
    public static abstract class GithubDataBase extends RoomDatabase {

        public abstract GithubDataDao githubDataDao();
    }

    public static class Repo {

        public List< GithubData > items;
    }

    @OptIn( markerClass = ExperimentalPagingApi.class )
    public static class GithubRemoteMediator extends RxRemoteMediator< Integer, GithubData > {

        private final GithubDataDao mDao;

        private final HTTP http = HTTP.builder().build();

        public GithubRemoteMediator( GithubDataDao dao ) {

            mDao = dao;
        }

        @NonNull
        @Override
        public Single< InitializeAction > initializeSingle() {

            return super.initializeSingle();
        }

        @NonNull
        @Override
        public Single< MediatorResult > loadSingle( @NonNull LoadType loadType,
            @NonNull PagingState< Integer, GithubData > state ) {

            Integer nextPage = 1;
            // The network load method takes an optional after=<user.id> parameter. For
            // every page after the first, pass the last user ID to let it continue from
            // where it left off. For REFRESH, pass null to load the first page.
            switch ( loadType ) {
                case REFRESH:
                    break;
                case PREPEND:
                    // In this example, you never need to prepend, since REFRESH will always
                    // load the first page in the list. Immediately return, reporting end of
                    // pagination.
                    return Single.just( new Success( true ) );
                case APPEND:
                    GithubData lastItem = state.lastItemOrNull();

                    // You must explicitly check if the last item is null when appending,
                    // since passing null to networkService is only valid for initial load.
                    // If lastItem is null it means no items were loaded after the initial
                    // REFRESH and there are no more items to load.
                    if ( lastItem == null ) {
                        return Single.just( new Success( true ) );
                    }
                    Integer anchorPosition = state.getAnchorPosition();
                    if ( anchorPosition != null ) {
                        Page< Integer, GithubData > page = state.closestPageToPosition( anchorPosition );
                        if ( page != null ) {
                            nextPage = page.getNextKey();
                        }
                    }
                    break;
            }

            Integer key = nextPage;

            return Single.just( url )
                .subscribeOn( Schedulers.io() )
                .map( new Function< String, List< GithubData > >() {
                    @Override
                    public List< GithubData > apply( String s ) throws Throwable {

                        Reader inputStream = http.sync( url ).addUrlPara( "page", key ).addUrlPara( "per_page", 15 ).get()
                            .getBody().toCharStream();
                        Gson singletonGson = GsonFactory.getSingletonGson();
                        Repo repo = singletonGson.fromJson( inputStream, Repo.class );

                        if ( repo.items == null ) {
                            return new ArrayList<>();
                        }

                        return repo.items;
                    }
                } )
                .map( new Function< List< GithubData >, MediatorResult >() {
                    @Override
                    public MediatorResult apply( List< GithubData > data ) throws Throwable {

                        if ( loadType == LoadType.REFRESH ) {
                            mDao.clearAll();
                        }

                        mDao.insertAll( data );

                        return new Success( data.size() < 15 );
                    }
                } )
                .onErrorResumeNext( new Function< Throwable, SingleSource< ? extends MediatorResult > >() {
                    @Override
                    public SingleSource< ? extends MediatorResult > apply( Throwable e ) throws Throwable {

                        if ( e instanceof IOException || e instanceof OkHttpsException ) {
                            return Single.just( new Error( e ) );
                        }

                        return Single.error( e );
                    }
                } );
        }
    }

    public static class Adapter extends PagingDataAdapter< GithubData, ViewHolder > {

        public Adapter() {

            super( new Comparator() );
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder( @NonNull ViewGroup parent, int viewType ) {

            View view = LayoutInflater.from( parent.getContext() ).inflate( R.layout.activity_main51_item, parent, false );
            return new ViewHolder( view );
        }

        @Override
        public void onBindViewHolder( @NonNull ViewHolder holder, int position ) {

            GithubData item = getItem( position );
            if ( item != null ) {
                holder.bind( item );
            }
        }
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private final TextView mNameText;
        private final TextView mDescriptionText;
        private final TextView mStarCountText;


        public ViewHolder( @NonNull View itemView ) {

            super( itemView );

            mNameText = (TextView) itemView.findViewById( R.id.name_text );
            mDescriptionText = (TextView) itemView.findViewById( R.id.description_text );
            mStarCountText = (TextView) itemView.findViewById( R.id.star_count_text );
        }

        @SuppressLint( "DefaultLocale" )
        private void bind( GithubData item ) {

            mNameText.setText( item.name );
            mDescriptionText.setText( item.description );
            mStarCountText.setText( String.format( "%d", item.stargazers_count ) );
        }
    }

    public static class Comparator extends ItemCallback< GithubData > {

        @Override
        public boolean areItemsTheSame( @NonNull GithubData oldItem, @NonNull GithubData newItem ) {

            return Objects.equals( oldItem.id, newItem.id );
        }

        @Override
        public boolean areContentsTheSame( @NonNull GithubData oldItem, @NonNull GithubData newItem ) {

            return Objects.equals( oldItem, newItem );
        }
    }
}